package com.nx.platform.es.bean.modle.query;

import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import com.nx.platform.es.biz.wrapper.parser.StatementOperator;
import com.nx.platform.es.bean.dto.RequestContext;
import com.nx.platform.es.common.utils.CommonUtils;
import org.apache.commons.collections4.MapUtils;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author
 * @since 2016年10月15日
 */
public class GeoPointHandler implements QueryFieldHandler {

    private static final Pattern PATTERN_GEO_DISTANCE = Pattern.compile(
            "^(-?\\d+(?:\\.\\d+)?+),(-?\\d+(?:\\.\\d+)?+),(\\d+(?:\\.\\d+)?+)(m|km)?$");
    private static final Pattern PATTERN_GEO_DISTANCE_RANGE = Pattern.compile(
            "^(-?\\d+(?:\\.\\d+)?+),(-?\\d+(?:\\.\\d+)?+),(?:\\*|(\\d+(?:\\.\\d+)?+)(m|km)?)_(?:\\*|(\\d+(?:\\.\\d+)?+)(m|km)?)$");

    @Override
    public void handle(ImmutableMap<String, ?> fieldConfig, RequestContext context, String fieldFace,
                       StatementOperator operator, String fieldValue, ListMultimap<Boolean, QueryBuilder> queryBuilders) {
        // 空值
        if (Strings.isNullOrEmpty(fieldValue)) {
            return;
        }
        String face = MapUtils.getString(fieldConfig, "face");
        String fieldName = MapUtils.getString(fieldConfig, "fieldName");
        Preconditions.checkState(!Strings.isNullOrEmpty(fieldName));
        // 单个值
        Matcher singleMatcher = PATTERN_GEO_DISTANCE.matcher(fieldValue);
        if (singleMatcher.find()) {
            double lon = Double.parseDouble(singleMatcher.group(1));
            double lat = Double.parseDouble(singleMatcher.group(2));
            int distance = Integer.parseInt(singleMatcher.group(3));
            DistanceUnit unit = DistanceUnit.parseUnit(Strings.nullToEmpty(singleMatcher.group(4)),
                    DistanceUnit.KILOMETERS);
            // 记录Query信息(Trace)
            String geohash = CommonUtils.geohash(lon, lat, context.getGeohashLevel());
            context.getTrace().append("&").append(fieldFace).append(operator.getSymbol())
                    .append(geohash).append(",").append(distance).append(unit);
            //
            queryBuilders.put(operator.isSign(),
                    QueryBuilders.geoDistanceQuery(fieldName).point(lat, lon).distance(distance, unit));
            return;
        }
        // 区间
        Matcher rangeMatcher = PATTERN_GEO_DISTANCE_RANGE.matcher(fieldValue);
        if (rangeMatcher.find()) {
            double lon = Double.parseDouble(rangeMatcher.group(1));
            double lat = Double.parseDouble(rangeMatcher.group(2));
            double leftMills = parse(rangeMatcher.group(3), rangeMatcher.group(4));
            double rightMills = parse(rangeMatcher.group(5), rangeMatcher.group(6));
            // 记录Query信息(Trace)
            String geohash = CommonUtils.geohash(lon, lat, context.getGeohashLevel());
            context.getTrace().append("&").append(fieldFace).append(operator.getSymbol())
                    .append(geohash).append(",").append(leftMills).append("_").append(rightMills);
            //
            if (leftMills > 0 && rightMills > 0 && leftMills < rightMills) {
                queryBuilders.put(operator.isSign(),
                        QueryBuilders.geoDistanceQuery(fieldName).point(lat, lon).distance(rightMills,
                                DistanceUnit.METERS));
                queryBuilders.put(!operator.isSign(),
                        QueryBuilders.geoDistanceQuery(fieldName).point(lat, lon).distance(leftMills,
                                DistanceUnit.METERS));
                return;
            } else if (leftMills > 0) {
                queryBuilders.put(!operator.isSign(),
                        QueryBuilders.geoDistanceQuery(fieldName).point(lat, lon).distance(leftMills,
                                DistanceUnit.METERS));
                return;
            } else if (rightMills > 0) {
                queryBuilders.put(operator.isSign(),
                        QueryBuilders.geoDistanceQuery(fieldName).point(lat, lon).distance(rightMills,
                                DistanceUnit.METERS));
                return;
            }
        }
        throw new IllegalArgumentException("format incorrect: field=" + face + ", value=" + fieldValue);
    }

    private double parse(String value, String unit) {
        if (value == null) {
            return 0d;
        }
        return DistanceUnit.METERS.convert(Double.parseDouble(value),
                DistanceUnit.parseUnit(Strings.nullToEmpty(unit), DistanceUnit.KILOMETERS));
    }

}
